/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "../renderer/r_local.h"
#include "win_local.h"


#define WINDOW_NAME					ENGINE_NAME
#define	WINDOW_CLASS_MAIN			ENGINE_NAME " MainWnd"
#define	WINDOW_CLASS_FAKE			ENGINE_NAME " FakeWnd"
#define WINDOW_STYLE_FULLSCREEN		(WS_POPUP)
#define WINDOW_STYLE_WINDOWED		(WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU)

typedef struct {
	bool			accelerated;
	bool			drawToWindow;
	bool			supportOpenGL;
	bool			doubleBuffer;
	bool			rgba;
	int				colorBits;
	int				alphaBits;
	int				depthBits;
	int				stencilBits;
	int				samples;
} glwPixelFormatDescriptor_t;

typedef struct {
	HWND			hWnd;
	HDC				hDC;
	HGLRC			hGLRC;

	HWND			hWndFake;
	HDC				hDCFake;
	HGLRC			hGLRCFake;

	WORD			deviceGammaRamp[768];
} glwState_t;

static glwState_t	glwState;

static const char *	(WINAPI * qwglGetExtensionsStringARB)(HDC hDC);
static BOOL			(WINAPI * qwglGetPixelFormatAttribivARB)(HDC hDC, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues);


/*
 ==================
 GLW_IsExtensionPresent
 ==================
*/
static bool GLW_IsExtensionPresent (const char *extName){

	script_t	*script;

	script = PS_LoadScriptMemory("IsExtensionPresent", glConfig.extensionsString, Str_Length(glConfig.extensionsString));
	if (!script)
		return false;

	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_PARSEGENERIC);

	if (!PS_SkipUntilString(script, extName)){
		PS_FreeScript(script);
		return false;
	}

	PS_FreeScript(script);

	return true;
}

/*
 ==================
 GLW_GetProcAddress
 ==================
*/
static void *GLW_GetProcAddress (const char *procName){

	void	*procAddress;

	procAddress = (void*)qwglGetProcAddress(procName);
	if (!procAddress){
		GLW_Shutdown();

		Com_Error(true, "GLW_GetProcAddress: wglGetProcAddress() failed for '%s'", procName);
	}

	return procAddress;
}

/*
 ==================
 GLW_InitExtensions
 ==================
*/
static void GLW_InitExtensions (void){

	Com_Printf("Initializing OpenGL extensions\n");

	// We must treat these as extensions, because on Windows we're still forced
	// to deal with the old GL 1.1 core.
	// Only the necessary function pointers from the GL 2.0 core will be
	// retrieved.
	qglActiveTexture = (GLACTIVETEXTURE)GLW_GetProcAddress("glActiveTexture");

	qglGenBuffers = (GLGENBUFFERS)GLW_GetProcAddress("glGenBuffers");
	qglDeleteBuffers = (GLDELETEBUFFERS)GLW_GetProcAddress("glDeleteBuffers");
	qglBindBuffer = (GLBINDBUFFER)GLW_GetProcAddress("glBindBuffer");
	qglBufferData = (GLBUFFERDATA)GLW_GetProcAddress("glBufferData");
	qglBufferSubData = (GLBUFFERSUBDATA)GLW_GetProcAddress("glBufferSubData");
	qglMapBuffer = (GLMAPBUFFER)GLW_GetProcAddress("glMapBuffer");
	qglUnmapBuffer = (GLUNMAPBUFFER)GLW_GetProcAddress("glUnmapBuffer");

	qglCreateShader = (GLCREATESHADER)GLW_GetProcAddress("glCreateShader");
	qglCreateProgram = (GLCREATEPROGRAM)GLW_GetProcAddress("glCreateProgram");
	qglDeleteShader = (GLDELETESHADER)GLW_GetProcAddress("glDeleteShader");
	qglDeleteProgram = (GLDELETEPROGRAM)GLW_GetProcAddress("glDeleteProgram");
	qglGetShaderiv = (GLGETSHADERIV)GLW_GetProcAddress("glGetShaderiv");
	qglGetProgramiv = (GLGETPROGRAMIV)GLW_GetProcAddress("glGetProgramiv");
	qglGetShaderInfoLog = (GLGETSHADERINFOLOG)GLW_GetProcAddress("glGetShaderInfoLog");
	qglGetProgramInfoLog = (GLGETPROGRAMINFOLOG)GLW_GetProcAddress("glGetProgramInfoLog");
	qglShaderSource = (GLSHADERSOURCE)GLW_GetProcAddress("glShaderSource");
	qglCompileShader = (GLCOMPILESHADER)GLW_GetProcAddress("glCompileShader");
	qglAttachShader = (GLATTACHSHADER)GLW_GetProcAddress("glAttachShader");
	qglDetachShader = (GLDETACHSHADER)GLW_GetProcAddress("glDetachShader");
	qglLinkProgram = (GLLINKPROGRAM)GLW_GetProcAddress("glLinkProgram");
	qglUseProgram = (GLUSEPROGRAM)GLW_GetProcAddress("glUseProgram");
	qglVertexAttribPointer = (GLVERTEXATTRIBPOINTER)GLW_GetProcAddress("glVertexAttribPointer");
	qglEnableVertexAttribArray = (GLENABLEVERTEXATTRIBARRAY)GLW_GetProcAddress("glEnableVertexAttribArray");
	qglDisableVertexAttribArray = (GLDISABLEVERTEXATTRIBARRAY)GLW_GetProcAddress("glDisableVertexAttribArray");
	qglBindAttribLocation = (GLBINDATTRIBLOCATION)GLW_GetProcAddress("glBindAttribLocation");
	qglGetAttribLocation = (GLGETATTRIBLOCATION)GLW_GetProcAddress("glGetAttribLocation");
	qglGetActiveUniform = (GLGETACTIVEUNIFORM)GLW_GetProcAddress("glGetActiveUniform");
	qglGetUniformLocation = (GLGETUNIFORMLOCATION)GLW_GetProcAddress("glGetUniformLocation");
	qglUniform1f = (GLUNIFORM1F)GLW_GetProcAddress("glUniform1f");
	qglUniform2f = (GLUNIFORM2F)GLW_GetProcAddress("glUniform2f");
	qglUniform3f = (GLUNIFORM3F)GLW_GetProcAddress("glUniform3f");
	qglUniform4f = (GLUNIFORM4F)GLW_GetProcAddress("glUniform4f");
	qglUniform1i = (GLUNIFORM1I)GLW_GetProcAddress("glUniform1i");
	qglUniform2i = (GLUNIFORM2I)GLW_GetProcAddress("glUniform2i");
	qglUniform3i = (GLUNIFORM3I)GLW_GetProcAddress("glUniform3i");
	qglUniform4i = (GLUNIFORM4I)GLW_GetProcAddress("glUniform4i");
	qglUniform1fv = (GLUNIFORM1FV)GLW_GetProcAddress("glUniform1fv");
	qglUniform2fv = (GLUNIFORM2FV)GLW_GetProcAddress("glUniform2fv");
	qglUniform3fv = (GLUNIFORM3FV)GLW_GetProcAddress("glUniform3fv");
	qglUniform4fv = (GLUNIFORM4FV)GLW_GetProcAddress("glUniform4fv");
	qglUniform1iv = (GLUNIFORM1IV)GLW_GetProcAddress("glUniform1iv");
	qglUniform2iv = (GLUNIFORM2IV)GLW_GetProcAddress("glUniform2iv");
	qglUniform3iv = (GLUNIFORM3IV)GLW_GetProcAddress("glUniform3iv");
	qglUniform4iv = (GLUNIFORM4IV)GLW_GetProcAddress("glUniform4iv");
	qglUniformMatrix2fv = (GLUNIFORMMATRIX2FV)GLW_GetProcAddress("glUniformMatrix2fv");
	qglUniformMatrix3fv = (GLUNIFORMMATRIX3FV)GLW_GetProcAddress("glUniformMatrix3fv");
	qglUniformMatrix4fv = (GLUNIFORMMATRIX4FV)GLW_GetProcAddress("glUniformMatrix4fv");

	qglStencilFuncSeparate = (GLSTENCILFUNCSEPARATE)GLW_GetProcAddress("glStencilFuncSeparate");
	qglStencilOpSeparate = (GLSTENCILOPSEPARATE)GLW_GetProcAddress("glStencilOpSeparate");
	qglStencilMaskSeparate = (GLSTENCILMASKSEPARATE)GLW_GetProcAddress("glStencilMaskSeparate");

	// HACK: some implementations may advertise a GL 2.0 core but still lack
	// support for some features, so by checking if this extension is available
	// we can tell if non-power of two textures are hardware accelerated and
	// avoid an otherwise software fallback path
	if (GLW_IsExtensionPresent("GL_ARB_texture_non_power_of_two"))
		glConfig.textureNonPowerOfTwoAvailable = true;

	// Now initialize the actual extensions
	if (GLW_IsExtensionPresent("GL_EXT_texture_filter_anisotropic")){
		glConfig.textureFilterAnisotropicAvailable = true;

		qglGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.maxTextureMaxAnisotropy);

		Com_Printf("...using GL_EXT_texture_filter_anisotropic\n");
	}
	else
		Com_Printf("...GL_EXT_texture_filter_anisotropic not found\n");

	if (GLW_IsExtensionPresent("GL_EXT_depth_bounds_test")){
		glConfig.depthBoundsTestAvailable = true;

		qglDepthBoundsEXT = (GLDEPTHBOUNDSEXT)GLW_GetProcAddress("glDepthBoundsEXT");

		Com_Printf("...using GL_EXT_depth_bounds_test\n");
	}
	else
		Com_Printf("...GL_EXT_depth_bounds_test not found\n");

	if (GLW_IsExtensionPresent("WGL_EXT_swap_control")){
		glConfig.swapControlAvailable = true;

		qwglSwapIntervalEXT = (WGLSWAPINTERVALEXT)GLW_GetProcAddress("wglSwapIntervalEXT");

		Com_Printf("...using WGL_EXT_swap_control\n");
	}
	else
		Com_Printf("...WGL_EXT_swap_control not found\n");
}

/*
 ==================
 GLW_InitFakeOpenGL

 Fake OpenGL stuff to work around the crappy WGL limitations.
 Do this silently.
 ==================
*/
static void GLW_InitFakeOpenGL (void){

	WNDCLASSEX				wndClass;
	PIXELFORMATDESCRIPTOR	PFD;
	int						pixelFormat;

	// Register the frame class
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = 0;
	wndClass.lpfnWndProc = WIN_FakeWindowProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = sys.hInstance;
	wndClass.hIcon = LoadIcon(sys.hInstance, MAKEINTRESOURCE(IDI_ICON1));
	wndClass.hIconSm = 0;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)COLOR_GRAYTEXT;
	wndClass.lpszMenuName = 0;
	wndClass.lpszClassName = WINDOW_CLASS_FAKE;

	if (!RegisterClassEx(&wndClass))
		return;

	// Create the fake window
	if ((glwState.hWndFake = CreateWindowEx(0, WINDOW_CLASS_FAKE, WINDOW_NAME, WINDOW_STYLE_WINDOWED, 0, 0, 320, 240, NULL, NULL, sys.hInstance, NULL)) == NULL){
		UnregisterClass(WINDOW_CLASS_FAKE, sys.hInstance);
		return;
	}

	// Get a DC for the fake window
	if ((glwState.hDCFake = GetDC(glwState.hWndFake)) == NULL)
		return;

	// Choose a pixel format
	memset(&PFD, 0, sizeof(PIXELFORMATDESCRIPTOR));

	PFD.cColorBits = 32;
	PFD.cDepthBits = 24;
	PFD.cStencilBits = 8;
	PFD.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	PFD.iLayerType = PFD_MAIN_PLANE;
	PFD.iPixelType = PFD_TYPE_RGBA;
	PFD.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	PFD.nVersion = 1;

	pixelFormat = ChoosePixelFormat(glwState.hDCFake, &PFD);
	if (!pixelFormat)
		return;

	// Set the pixel format
	DescribePixelFormat(glwState.hDCFake, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &PFD);

	if (!SetPixelFormat(glwState.hDCFake, pixelFormat, &PFD))
		return;

	// Create the fake GL context and make it current
	if ((glwState.hGLRCFake = qwglCreateContext(glwState.hDCFake)) == NULL)
		return;

	if (!qwglMakeCurrent(glwState.hDCFake, glwState.hGLRCFake))
		return;

	// We only need this function pointer, if available
	qwglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)qwglGetProcAddress("wglGetPixelFormatAttribivARB");
}

/*
 ==================
 GLW_ShutdownFakeOpenGL

 Fake OpenGL stuff to work around the crappy WGL limitations.
 Do this silently.
 ==================
*/
static void GLW_ShutdownFakeOpenGL (void){

	if (glwState.hGLRCFake){
		if (qwglMakeCurrent)
			qwglMakeCurrent(NULL, NULL);

		if (qwglDeleteContext)
			qwglDeleteContext(glwState.hGLRCFake);

		glwState.hGLRCFake = NULL;
	}

	if (glwState.hDCFake){
		ReleaseDC(glwState.hWndFake, glwState.hDCFake);
		glwState.hDCFake = NULL;
	}

	if (glwState.hWndFake){
		DestroyWindow(glwState.hWndFake);
		glwState.hWndFake = NULL;

		UnregisterClass(WINDOW_CLASS_FAKE, sys.hInstance);
	}

	qwglGetPixelFormatAttribivARB = NULL;
}

/*
 ==================
 GLW_DescribePixelFormat
 ==================
*/
static void GLW_DescribePixelFormat (int pixelFormat, glwPixelFormatDescriptor_t *pfd){

	PIXELFORMATDESCRIPTOR	gdiPFD;
	int						wglAttribs[11], wglValues[11];

	if (qwglGetPixelFormatAttribivARB){
		wglAttribs[0] = WGL_ACCELERATION_ARB;
		wglAttribs[1] = WGL_DRAW_TO_WINDOW_ARB;
		wglAttribs[2] = WGL_SUPPORT_OPENGL_ARB;
		wglAttribs[3] = WGL_DOUBLE_BUFFER_ARB;
		wglAttribs[4] = WGL_PIXEL_TYPE_ARB;
		wglAttribs[5] = WGL_COLOR_BITS_ARB;
		wglAttribs[6] = WGL_ALPHA_BITS_ARB;
		wglAttribs[7] = WGL_DEPTH_BITS_ARB;
		wglAttribs[8] = WGL_STENCIL_BITS_ARB;
		wglAttribs[9] = WGL_SAMPLE_BUFFERS_ARB;
		wglAttribs[10] = WGL_SAMPLES_ARB;

		if (!qwglGetPixelFormatAttribivARB(glwState.hDC, pixelFormat, 0, 11, wglAttribs, wglValues)){
			// If failed, WGL_ARB_multisample is not supported
			qwglGetPixelFormatAttribivARB(glwState.hDC, pixelFormat, 0, 9, wglAttribs, wglValues);

			wglValues[9] = GL_FALSE;
			wglValues[10] = 0;
		}

		pfd->accelerated = (wglValues[0] == WGL_FULL_ACCELERATION_ARB);
		pfd->drawToWindow = (wglValues[1] == GL_TRUE);
		pfd->supportOpenGL = (wglValues[2] == GL_TRUE);
		pfd->doubleBuffer = (wglValues[3] == GL_TRUE);
		pfd->rgba = (wglValues[4] == WGL_TYPE_RGBA_ARB);
		pfd->colorBits = wglValues[5];
		pfd->alphaBits = wglValues[6];
		pfd->depthBits = wglValues[7];
		pfd->stencilBits = wglValues[8];
		pfd->samples = (wglValues[9] == GL_TRUE) ? wglValues[10] : 0;
	}
	else {
		DescribePixelFormat(glwState.hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &gdiPFD);

		pfd->accelerated = !(gdiPFD.dwFlags & PFD_GENERIC_FORMAT);
		pfd->drawToWindow = (gdiPFD.dwFlags & PFD_DRAW_TO_WINDOW);
		pfd->supportOpenGL = (gdiPFD.dwFlags & PFD_SUPPORT_OPENGL);
		pfd->doubleBuffer = (gdiPFD.dwFlags & PFD_DOUBLEBUFFER);
		pfd->rgba = (gdiPFD.iPixelType == PFD_TYPE_RGBA);
		pfd->colorBits = gdiPFD.cColorBits;
		pfd->alphaBits = gdiPFD.cAlphaBits;
		pfd->depthBits = gdiPFD.cDepthBits;
		pfd->stencilBits = gdiPFD.cStencilBits;
		pfd->samples = 0;
	}
}

/*
 ==================
 GLW_ChoosePixelFormat
 ==================
*/
static int GLW_ChoosePixelFormat (int colorBits, int alphaBits, int depthBits, int stencilBits, int samples){

	glwPixelFormatDescriptor_t	current, selected;
	int							i, numPixelFormats, pixelFormat = 0;

	// Initialize the fake OpenGL stuff so that we can use the extended pixel
	// format functionality
	GLW_InitFakeOpenGL();

	// Count pixel formats
	if (qwglGetPixelFormatAttribivARB){
		numPixelFormats = WGL_NUMBER_PIXEL_FORMATS_ARB;

		qwglGetPixelFormatAttribivARB(glwState.hDC, 0, 0, 1, &numPixelFormats, &numPixelFormats);
	}
	else
		numPixelFormats = DescribePixelFormat(glwState.hDC, 0, 0, NULL);

	if (numPixelFormats < 1){
		Com_Printf("...no pixel formats found\n");

		GLW_ShutdownFakeOpenGL();

		return 0;
	}

	Com_Printf("...%i pixel formats found\n", numPixelFormats);

	// Check if multisampling is desired
	if (samples > 0)
		Com_Printf("...attempting to use multisampling\n");
	else
		samples = 0;

	// Run through all the pixel formats, looking for the best match
	for (i = 1; i <= numPixelFormats; i++){
		// Describe the current pixel format
		GLW_DescribePixelFormat(i, &current);

		// Check acceleration
		if (!current.accelerated){
			Com_DPrintf("...PIXELFORMAT %i rejected, software emulation\n", i);
			continue;
		}

		// Check samples
		if (current.samples && !samples){
			Com_DPrintf("...PIXELFORMAT %i rejected, multisample\n", i);
			continue;
		}

		// Check flags
		if (!current.drawToWindow || !current.supportOpenGL || !current.doubleBuffer){
			Com_DPrintf("...PIXELFORMAT %i rejected, improper flags\n", i);
			continue;
		}

		// Check pixel type
		if (!current.rgba){
			Com_DPrintf("...PIXELFORMAT %i rejected, not RGBA\n", i);
			continue;
		}

		// Check color bits
		if (current.colorBits < colorBits){
			Com_DPrintf("...PIXELFORMAT %i rejected, insufficient color bits (%i < %i)\n", i, current.colorBits, colorBits);
			continue;
		}

		// Check alpha bits
		if (current.alphaBits < alphaBits){
			Com_DPrintf("...PIXELFORMAT %i rejected, insufficient alpha bits (%i < %i)\n", i, current.alphaBits, alphaBits);
			continue;
		}

		// Check depth bits
		if (current.depthBits < depthBits){
			Com_DPrintf("...PIXELFORMAT %i rejected, insufficient depth bits (%i < %i)\n", i, current.depthBits, depthBits);
			continue;
		}

		// Check stencil bits
		if (current.stencilBits < stencilBits){
			Com_DPrintf("...PIXELFORMAT %i rejected, insufficient stencil bits (%i < %i)\n", i, current.stencilBits, stencilBits);
			continue;
		}

		// If we don't have a selected pixel format yet, then use it
		if (!pixelFormat){
			selected = current;
			pixelFormat = i;
			continue;
		}

		// If current pixel format is better than selected pixel format, then
		// use it
		if (selected.samples != samples){
			if (current.samples == samples || current.samples > selected.samples){
				selected = current;
				pixelFormat = i;
				continue;
			}
		}

		if (selected.colorBits != colorBits){
			if (current.colorBits == colorBits || current.colorBits > selected.colorBits){
				selected = current;
				pixelFormat = i;
				continue;
			}
		}

		if (selected.alphaBits != alphaBits){
			if (current.alphaBits == alphaBits || current.alphaBits > selected.alphaBits){
				selected = current;
				pixelFormat = i;
				continue;
			}
		}

		if (selected.depthBits != depthBits){
			if (current.depthBits == depthBits || current.depthBits > selected.depthBits){
				selected = current;
				pixelFormat = i;
				continue;
			}
		}

		if (selected.stencilBits != stencilBits){
			if (current.stencilBits == stencilBits || current.stencilBits > selected.stencilBits){
				selected = current;
				pixelFormat = i;
				continue;
			}
		}
	}

	// Shutdown the fake OpenGL stuff since we no longer need it
	GLW_ShutdownFakeOpenGL();

	// Make sure we have a valid pixel format
	if (!pixelFormat){
		Com_Printf("...no hardware acceleration found\n");
		return 0;
	}

	Com_Printf("...hardware acceleration found\n");

	// Report if multisampling is desired but unavailable
	if (samples && !selected.samples)
		Com_Printf("...failed to select a multisample PIXELFORMAT\n");

	// Get the pixel format
	glConfig.colorBits = selected.colorBits;
	glConfig.alphaBits = selected.alphaBits;
	glConfig.depthBits = selected.depthBits;
	glConfig.stencilBits = selected.stencilBits;
	glConfig.samples = selected.samples;

	Com_Printf("...PIXELFORMAT %i selected\n", pixelFormat);

	return pixelFormat;
}

/*
 ==================
 GLW_InitDriver
 ==================
*/
static bool GLW_InitDriver (void){

	PIXELFORMATDESCRIPTOR	PFD;
	int						pixelFormat;

	Com_Printf("Initializing OpenGL driver\n");

	// Get a DC for the current window
	Com_Printf("...getting DC: ");
	if ((glwState.hDC = GetDC(glwState.hWnd)) == NULL){
		Com_Printf("failed\n" );
		return false;
	}
	Com_Printf("succeeded\n");

	// Get the device gamma ramp
	Com_Printf("...getting gamma ramp: ");
	if ((glConfig.deviceSupportsGamma = GetDeviceGammaRamp(glwState.hDC, glwState.deviceGammaRamp)) == false)
		Com_Printf("failed\n");
	else
		Com_Printf("succeeded\n");

	// Choose a pixel format
	pixelFormat = GLW_ChoosePixelFormat(32, 8, 24, 8, r_multiSamples->integerValue);
	if (!pixelFormat){
		Com_Printf("...failed to find an appropriate PIXELFORMAT\n");

		ReleaseDC(glwState.hWnd, glwState.hDC);
		glwState.hDC = NULL;

		return false;
	}

	// Set the pixel format
	DescribePixelFormat(glwState.hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &PFD);

	if (!SetPixelFormat(glwState.hDC, pixelFormat, &PFD)){
		Com_Printf("...failed to set PIXELFORMAT\n");

		ReleaseDC(glwState.hWnd, glwState.hDC);
		glwState.hDC = NULL;

		return false;
	}

	// Create the GL context and make it current
	Com_Printf("...creating GL context: ");
	if ((glwState.hGLRC = qwglCreateContext(glwState.hDC)) == NULL){
		Com_Printf("failed\n");

		ReleaseDC(glwState.hWnd, glwState.hDC);
		glwState.hDC = NULL;

		return false;
	}
	Com_Printf("succeeded\n");

	Com_Printf("...making context current: ");
	if (!qwglMakeCurrent(glwState.hDC, glwState.hGLRC)){
		Com_Printf("failed\n");

		qwglDeleteContext(glwState.hGLRC);
		glwState.hGLRC = NULL;

		ReleaseDC(glwState.hWnd, glwState.hDC);
		glwState.hDC = NULL;

		return false;
	}
	Com_Printf("succeeded\n");

	return true;
}

/*
 ==================
 GLW_CreateWindow
 ==================
*/
static bool GLW_CreateWindow (void){

	cvar_t		*xPos, *yPos;
	WNDCLASSEX	wndClass;
	RECT		rect;
	int			style, exStyle;
	int			screenWidth, screenHeight;
	int			x, y, w, h;

	// Get the window position and dimensions
	xPos = CVar_Get("win_xPos", "3", CVAR_ARCHIVE | CVAR_LATCH, "Horizontal window position");
	yPos = CVar_Get("win_yPos", "22", CVAR_ARCHIVE | CVAR_LATCH, "Vertical window position");

	if (glConfig.isFullscreen){
		style = WINDOW_STYLE_FULLSCREEN;
		exStyle = WS_EX_TOPMOST;

		x = 0;
		y = 0;
		w = glConfig.videoWidth;
		h = glConfig.videoHeight;
	}
	else {
		style = WINDOW_STYLE_WINDOWED;
		exStyle = 0;

		rect.left = 0;
		rect.top = 0;
		rect.right = glConfig.videoWidth;
		rect.bottom = glConfig.videoHeight;

		AdjustWindowRectEx(&rect, style, FALSE, exStyle);

		x = xPos->integerValue;
		y = yPos->integerValue;
		w = rect.right - rect.left;
		h = rect.bottom - rect.top;

		// Adjust window coordinates if necessary so that the window is
		// completely on screen
		screenWidth = GetSystemMetrics(SM_CXSCREEN);
		screenHeight = GetSystemMetrics(SM_CYSCREEN);

		if (x < 0)
			x = 0;
		if (y < 0)
			y = 0;

		if (w < screenWidth && h < screenHeight){
			if (x + w > screenWidth)
				x = screenWidth - w;
			if (y + h > screenHeight)
				y = screenHeight - h;
		}
		else {
			x = 0;
			y = 0;
		}
	}

	// Let the system code know if we're fullscreen
	sys.isFullscreen = glConfig.isFullscreen;

	// Register the frame class
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = 0;
	wndClass.lpfnWndProc = WIN_MainWindowProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = sys.hInstance;
	wndClass.hIcon = LoadIcon(sys.hInstance, MAKEINTRESOURCE(IDI_ICON1));
	wndClass.hIconSm = 0;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)COLOR_GRAYTEXT;
	wndClass.lpszMenuName = 0;
	wndClass.lpszClassName = WINDOW_CLASS_MAIN;

	if (!RegisterClassEx(&wndClass)){
		Com_Printf("...failed to register window class\n");
		return false;
	}
	Com_Printf("...registered window class\n");

	// Create the window
	if ((glwState.hWnd = CreateWindowEx(exStyle, WINDOW_CLASS_MAIN, WINDOW_NAME, style, x, y, w, h, NULL, NULL, sys.hInstance, NULL)) == NULL){
		Com_Printf("...failed to create window\n");

		UnregisterClass(WINDOW_CLASS_MAIN, sys.hInstance);

		return false;
	}
	Com_Printf("...created window @ %i,%i (%ix%i)\n", x, y, w, h);

	// Show the window
	ShowWindow(glwState.hWnd, SW_SHOW);
	UpdateWindow(glwState.hWnd);
	SetForegroundWindow(glwState.hWnd);
	SetFocus(glwState.hWnd);

	// Create the context
	if (!GLW_InitDriver()){
		Com_Printf("...destroying window\n");

		ShowWindow(glwState.hWnd, SW_HIDE);
		DestroyWindow(glwState.hWnd);
		glwState.hWnd = NULL;

		UnregisterClass(WINDOW_CLASS_MAIN, sys.hInstance);

		return false;
	}

	return true;
}

/*
 ==================
 GLW_SetDisplayMode
 ==================
*/
static bool GLW_SetDisplayMode (void){

	DEVMODE	devMode;
	HDC		hDC;
	int		displayDepth;
	int		cdsError;

	Com_Printf("...setting mode %i: ", r_mode->integerValue);
	if (!R_GetVideoModeInfo(r_mode->integerValue, &glConfig.videoWidth, &glConfig.videoHeight)){
		Com_Printf("invalid mode\n");
		return false;
	}

	Com_Printf("%i %i ", glConfig.videoWidth, glConfig.videoHeight);

	if (r_fullscreen->integerValue){
		if (r_displayRefresh->integerValue > 0)
			Com_Printf("FS (%i Hz)\n", r_displayRefresh->integerValue);
		else
			Com_Printf("FS\n");
	}
	else
		Com_Printf("W\n");

	// Do a CDS if needed
	if (r_fullscreen->integerValue){
		memset(&devMode, 0, sizeof(DEVMODE));
		devMode.dmSize = sizeof(DEVMODE);

		devMode.dmPelsWidth = glConfig.videoWidth;
		devMode.dmPelsHeight = glConfig.videoHeight;
		devMode.dmBitsPerPel = 32;
		devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

		if (r_displayRefresh->integerValue > 0){
			devMode.dmDisplayFrequency = r_displayRefresh->integerValue;
			devMode.dmFields |= DM_DISPLAYFREQUENCY;
		}

		Com_Printf("...calling CDS: ");

		if ((cdsError = ChangeDisplaySettings(&devMode, CDS_FULLSCREEN)) == DISP_CHANGE_SUCCESSFUL){
			Com_Printf("ok\n");

			glConfig.isFullscreen = true;
		}
		else {
			switch (cdsError){
			case DISP_CHANGE_BADFLAGS:
				Com_Printf("bad flags\n");
				break;
			case DISP_CHANGE_BADMODE:
				Com_Printf("bad mode\n");
				break;
			case DISP_CHANGE_BADPARAM:
				Com_Printf("bad param\n");
				break;
			case DISP_CHANGE_FAILED:
				Com_Printf("failed\n");
				break;
			case DISP_CHANGE_NOTUPDATED:
				Com_Printf("not updated\n");
				break;
			case DISP_CHANGE_RESTART:
				Com_Printf("restart required\n");
				break;
			default:
				Com_Printf("unknown error (%i)\n", cdsError);
				break;
			}

			// Try safe 640x480 mode, unless we just tried it and failed
			if (glConfig.videoWidth != 640 || glConfig.videoHeight != 480){
				Com_Printf("...trying safe mode: ");

				memset(&devMode, 0, sizeof(DEVMODE));
				devMode.dmSize = sizeof(DEVMODE);

				devMode.dmPelsWidth = 640;
				devMode.dmPelsHeight = 480;
				devMode.dmBitsPerPel = 32;
				devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

				if ((cdsError = ChangeDisplaySettings(&devMode, CDS_FULLSCREEN)) == DISP_CHANGE_SUCCESSFUL){
					Com_Printf("ok\n");

					glConfig.isFullscreen = true;

					glConfig.videoWidth = 640;
					glConfig.videoHeight = 480;
				}
				else {
					switch (cdsError){
					case DISP_CHANGE_BADFLAGS:
						Com_Printf("bad flags\n");
						break;
					case DISP_CHANGE_BADMODE:
						Com_Printf("bad mode\n");
						break;
					case DISP_CHANGE_BADPARAM:
						Com_Printf("bad param\n");
						break;
					case DISP_CHANGE_FAILED:
						Com_Printf("failed\n");
						break;
					case DISP_CHANGE_NOTUPDATED:
						Com_Printf("not updated\n");
						break;
					case DISP_CHANGE_RESTART:
						Com_Printf("restart required\n");
						break;
					default:
						Com_Printf("unknown error (%i)\n", cdsError);
						break;
					}
				}
			}
		}
	}

	// Create the window
	if (!glConfig.isFullscreen){
		if (r_fullscreen->integerValue)
			Com_Printf("...falling back to windowed mode\n");
		else
			Com_Printf("...setting windowed mode\n");

		hDC = GetDC(0);
		displayDepth = GetDeviceCaps(hDC, BITSPIXEL);
		ReleaseDC(0, hDC);

		if (displayDepth != 32){
			Com_Printf("...windowed mode requires 32 bits desktop depth\n");
			return false;
		}
	}

	if (!GLW_CreateWindow()){
		if (glConfig.isFullscreen){
			Com_Printf("...restoring display settings\n");

			ChangeDisplaySettings(NULL, 0);
			glConfig.isFullscreen = false;
		}

		return false;
	}

	// Get the display frequency
	hDC = GetDC(0);
	glConfig.displayFrequency = GetDeviceCaps(hDC, VREFRESH);
	ReleaseDC(0, hDC);

	return true;
}

/*
 ==================
 GLW_SetDeviceGammaRamp
 ==================
*/
void GLW_SetDeviceGammaRamp (const byte *gammaTable){

	word	gammaRamp[3][256];
	word	v;
	int		i;

	if (!glConfig.deviceSupportsGamma)
		return;

	if (!glwState.hDC)
		return;

	for (i = 0; i < 256; i++){
		v = (((word)gammaTable[i]) << 8) | gammaTable[i];

		gammaRamp[0][i] = v;
		gammaRamp[1][i] = v;
		gammaRamp[2][i] = v;
	}

	if (!SetDeviceGammaRamp(glwState.hDC, gammaRamp))
		Com_Printf(S_COLOR_YELLOW "WARNING: SetDeviceGammaRamp() failed\n");
}

/*
 ==================
 GLW_ActivateContext
 ==================
*/
void GLW_ActivateContext (bool active){

	if (!glwState.hDC || !glwState.hGLRC)
		return;

	if (active){
		if (!qwglMakeCurrent(glwState.hDC, glwState.hGLRC))
			Com_Error(true, "GLW_ActivateContext: wglMakeCurrent() failed");
	}
	else {
		if (!qwglMakeCurrent(NULL, NULL))
			Com_Error(true, "GLW_ActivateContext: wglMakeCurrent() failed");
	}
}

/*
 ==================
 GLW_SwapBuffers
 ==================
*/
void GLW_SwapBuffers (void){

	if (r_swapInterval->modified){
		if (r_swapInterval->integerValue < 0)
			CVar_SetInteger(r_swapInterval, 0);

		if (glConfig.swapControlAvailable)
			qwglSwapIntervalEXT(r_swapInterval->integerValue);

		r_swapInterval->modified = false;
	}

	if (!r_frontBuffer->integerValue){
		if (!SwapBuffers(glwState.hDC))
			Com_Error(true, "GLW_SwapBuffers: SwapBuffers() failed");
	}
}

/*
 ==================
 GLW_Init
 ==================
*/
void GLW_Init (void){

	float	version;

	Com_Printf("Initializing OpenGL subsystem\n");

	// Initialize our QGL dynamic bindings
	if (!QGL_Init(r_glDriver->value))
		Com_Error(true, "GLW_Init: could not load OpenGL subsystem");

	// Initialize the display, window, context, etc...
	if (!GLW_SetDisplayMode()){
		QGL_Shutdown();

		Com_Error(true, "GLW_Init: could not load OpenGL subsystem");
	}

	// We now have a working OpenGL subsystem
	glConfig.initialized = true;

	// Check for GL 2.0 support
	version = Str_ToFloat((const char *)qglGetString(GL_VERSION));

	if (version < 2.0f){
		GLW_Shutdown();

		Com_Error(true, "The current video card / driver combination does not support OpenGL 2.0");
	}

	// Get GL strings
	glConfig.vendorString = (const char *)qglGetString(GL_VENDOR);
	glConfig.rendererString = (const char *)qglGetString(GL_RENDERER);
	glConfig.versionString = (const char *)qglGetString(GL_VERSION);
	glConfig.extensionsString = (const char *)qglGetString(GL_EXTENSIONS);

	// Get WGL strings, if possible
	qwglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)qwglGetProcAddress("wglGetExtensionsStringARB");
	if (qwglGetExtensionsStringARB)
		glConfig.wglExtensionsString = qwglGetExtensionsStringARB(glwState.hDC);
	else
		glConfig.wglExtensionsString = "";

	// Get GLSL version string
	glConfig.shadingLanguageVersionString = (const char *)qglGetString(GL_SHADING_LANGUAGE_VERSION);

	// Get implementation limits
	qglGetIntegerv(GL_MAX_TEXTURE_SIZE, &glConfig.maxTextureSize);
	qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &glConfig.maxCubeMapTextureSize);
	qglGetIntegerv(GL_MAX_TEXTURE_UNITS, &glConfig.maxTextureUnits);
	qglGetIntegerv(GL_MAX_TEXTURE_COORDS, &glConfig.maxTextureCoords);
	qglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &glConfig.maxTextureImageUnits);
	qglGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &glConfig.maxVertexTextureImageUnits);
	qglGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &glConfig.maxCombinedTextureImageUnits);
	qglGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &glConfig.maxVertexAttribs);
	qglGetIntegerv(GL_MAX_VARYING_FLOATS, &glConfig.maxVaryingFloats);
	qglGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &glConfig.maxVertexUniformComponents);
	qglGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &glConfig.maxFragmentUniformComponents);
	qglGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &glConfig.maxTextureLODBias);

	// Initialize extensions
	GLW_InitExtensions();

	// Enable logging if requested
	QGL_EnableLogging(r_logFile->integerValue);
}

/*
 ==================
 GLW_Shutdown
 ==================
*/
void GLW_Shutdown (void){

	if (!glConfig.initialized)
		return;

	Com_Printf("Shutting down OpenGL subsystem\n");

	if (glwState.hGLRC){
		if (qwglMakeCurrent){
			Com_Printf("...wglMakeCurrent( NULL, NULL ): ");
			if (!qwglMakeCurrent(NULL, NULL))
				Com_Printf("failed\n");
			else
				Com_Printf("succeeded\n");
		}

		if (qwglDeleteContext){
			Com_Printf("...deleting GL context: ");
			if (!qwglDeleteContext(glwState.hGLRC))
				Com_Printf("failed\n");
			else
				Com_Printf("succeeded\n");
		}

		glwState.hGLRC = NULL;
	}

	if (glwState.hDC){
		if (glConfig.deviceSupportsGamma){
			Com_Printf("...restoring gamma ramp: ");
			if (!SetDeviceGammaRamp(glwState.hDC, glwState.deviceGammaRamp))
				Com_Printf("failed\n");
			else
				Com_Printf("succeeded\n");
		}

		Com_Printf("...releasing DC: ");
		if (!ReleaseDC(glwState.hWnd, glwState.hDC))
			Com_Printf("failed\n");
		else
			Com_Printf("succeeded\n");

		glwState.hDC = NULL;
	}

	if (glwState.hWnd){
		Com_Printf("...destroying window\n");

		ShowWindow(glwState.hWnd, SW_HIDE);
		DestroyWindow(glwState.hWnd);
		glwState.hWnd = NULL;

		UnregisterClass(WINDOW_CLASS_MAIN, sys.hInstance);
	}

	if (glConfig.isFullscreen){
		Com_Printf("...restoring display settings\n");

		ChangeDisplaySettings(NULL, 0);
		glConfig.isFullscreen = false;
	}

	QGL_EnableLogging(false);

	QGL_Shutdown();

	memset(&glConfig, 0, sizeof(glConfig_t));
	memset(&glwState, 0, sizeof(glwState_t));
}
